# SPDX-License-Identifier: Apache-2.0

"""Admin Views related to Observations"""
from __future__ import annotations

import re

from datetime import datetime, timezone
from typing import TYPE_CHECKING

from pyramid.httpexceptions import HTTPSeeOther
from pyramid.view import view_config

from warehouse.authnz import Permissions
from warehouse.helpdesk.interfaces import IHelpDeskService
from warehouse.observations.models import Observation, ObservationKind
from warehouse.utils.project import (
    confirm_project,
    prohibit_and_remove_project,
    quarantine_project,
)

if TYPE_CHECKING:
    from pyramid.request import Request

    from warehouse.packaging.models import Project


@view_config(
    route_name="admin.malware_reports.list",
    renderer="warehouse.admin:templates/admin/malware_reports/list.html",
    permission=Permissions.AdminObservationsRead,
    request_method="GET",
    uses_session=True,
    require_csrf=True,
    require_methods=False,
)
def malware_reports_list(request):
    """
    List all Malware Reports.

    TODO: Paginate this view, not worthwhile just yet.
          Client-side pagination is a good enough solution for now.
    """
    malware_observations = (
        request.db.query(Observation)
        .filter(
            Observation.kind == "is_malware",
            Observation.related_id.isnot(None),
            Observation.actions == {},
        )
        .order_by(Observation.created)
        .all()
    )

    return {"malware_reports": malware_observations}


@view_config(
    route_name="admin.malware_reports.project.list",
    renderer="warehouse.admin:templates/admin/malware_reports/project_list.html",
    permission=Permissions.AdminObservationsRead,
    request_method="GET",
    uses_session=True,
    require_csrf=True,
    require_methods=False,
)
def malware_reports_project_list(project, request):
    """
    List all Malware Reports for a specific project.
    """
    malware_observations = (
        request.db.query(Observation)
        .filter(
            Observation.related_id == project.id,
            Observation.kind == "is_malware",
            Observation.actions == {},
        )
        .order_by(Observation.created)
        .all()
    )

    return {"project": project, "malware_reports": malware_observations}


@view_config(
    route_name="admin.malware_reports.project.verdict_not_malware",
    permission=Permissions.AdminObservationsWrite,
    uses_session=True,
    require_csrf=True,
    require_methods=["POST"],
)
def malware_reports_project_verdict_not_malware(project, request):
    """
    Mark all Observations for Project as not malware.
    """
    observations = (
        request.db.query(Observation)
        .filter(Observation.related_id == project.id)
        .filter(Observation.kind == "is_malware")
        .all()
    )

    now = datetime.now(tz=timezone.utc)

    for observation in observations:
        observation.actions[int(now.timestamp())] = {
            "actor": request.user.username,
            "action": "verdict_not_malware",
            "reason": request.POST.get("reason"),
            "created_at": str(now),
        }

    request.session.flash(
        f"Project {project.name} marked as not malware.\n"
        "Please update related Help Scout conversations.",
        queue="success",
    )
    return HTTPSeeOther(request.route_path("admin.malware_reports.list"))


@view_config(
    route_name="admin.malware_reports.project.verdict_quarantine",
    permission=Permissions.AdminObservationsWrite,
    uses_session=True,
    require_csrf=True,
    require_methods=["POST"],
)
def malware_reports_project_verdict_quarantine(
    project: Project, request: Request
) -> HTTPSeeOther:
    """
    Quarantine a Project. Reversible action.
    """
    quarantine_project(project, request)
    return HTTPSeeOther(request.route_path("admin.malware_reports.list"))


@view_config(
    route_name="admin.malware_reports.project.verdict_remove_malware",
    permission=Permissions.AdminProjectsDelete,
    uses_session=True,
    require_csrf=True,
    require_methods=["POST"],
)
def malware_reports_project_verdict_remove_malware(project, request):
    """
    Remove Project as malware, freeze associated user accounts,
    add action to all Malware Observations.
    """
    confirm_project(project, request, fail_route="admin.malware_reports.project.list")

    # freeze associated user accounts
    for user in project.users:
        user.is_frozen = True
        user.record_observation(
            request=request,
            kind=ObservationKind.AccountAbuse,
            actor=request.user,
            summary="Account frozen due to malware report",
            payload={"project_name": project.name},
        )

    # add action to all Malware Observations, **before we remove the project**
    observations = (
        request.db.query(Observation)
        .filter(Observation.related_id == project.id)
        .filter(Observation.kind == "is_malware")
        .all()
    )

    now = datetime.now(tz=timezone.utc)

    for observation in observations:
        observation.actions[int(now.timestamp())] = {
            "actor": request.user.username,
            "action": "remove_malware",
            "created_at": str(now),
        }

    # prohibit the project
    prohibit_and_remove_project(
        project, request, observation_kind=ObservationKind.IsMalware.value[0]
    )
    # tell helpdesk to add a tag to all related conversations
    helpdesk_service = request.find_service(IHelpDeskService)
    for observation in observations:
        helpdesk_service.add_tag(
            conversation_url=observation.additional["helpscout_conversation_url"],
            tag="removed_as_malware_via_admin",
        )

    request.session.flash(
        f"Malware Project {project.name} removed.\n"
        "Please update related Help Scout conversations.",
        queue="success",
    )
    return HTTPSeeOther(request.route_path("admin.malware_reports.list"))


@view_config(
    route_name="admin.malware_reports.detail",
    renderer="warehouse.admin:templates/admin/malware_reports/detail.html",
    permission=Permissions.AdminObservationsRead,
    request_method="GET",
    uses_session=True,
    require_csrf=True,
    require_methods=False,
)
def malware_reports_detail(request):
    """
    Show a detailed view of a Malware Report.
    """
    observation_id = request.matchdict.get("observation_id")
    observation = (
        request.db.get(Observation, observation_id) if observation_id else None
    )

    return {"report": observation}


@view_config(
    route_name="admin.malware_reports.detail.add_helpscout_conversation",
    permission=Permissions.AdminObservationsWrite,
    uses_session=True,
    require_csrf=True,
    require_methods=["POST"],
)
def add_helpscout_conversation(request):
    """
    Add a HelpScout Conversation URL to an Observation.
    """
    observation_id = request.matchdict.get("observation_id")
    observation = request.db.get(Observation, observation_id)

    helpscout_conversation_url = request.POST.get("helpscout_conversation_url")

    # Allow the user to paste any kind of Help Scout URL, parse out the conversation ID.
    match = re.match(
        r"https://(?:secure|api)\.helpscout\.net/(?:conversation|v2/conversations)/(\d+)",  # noqa: E501
        helpscout_conversation_url,
    )
    if not match:
        request.session.flash(
            "Please provide a valid HelpScout Conversation URL.",
            queue="error",
        )
        return HTTPSeeOther(
            request.route_path(
                "admin.malware_reports.detail", observation_id=observation_id
            )
        )

    conversation_id = match.group(1)
    observation.additional["helpscout_conversation_url"] = (
        f"https://api.helpscout.net/v2/conversations/{conversation_id}"
    )

    request.session.flash(
        f"HelpScout Conversation URL added to report for {observation.related.name}",
        queue="success",
    )

    return HTTPSeeOther(
        request.route_path(
            "admin.malware_reports.detail", observation_id=observation_id
        )
    )


@view_config(
    route_name="admin.malware_reports.detail.verdict_not_malware",
    permission=Permissions.AdminObservationsWrite,
    uses_session=True,
    require_csrf=True,
    require_methods=["POST"],
)
def not_malware_for_project(request):
    """
    Mark a Project as not malware.
    """
    observation_id = request.matchdict.get("observation_id")
    observation = request.db.get(Observation, observation_id)
    project = observation.related

    now = datetime.now(tz=timezone.utc)

    observation.actions[int(now.timestamp())] = {
        "actor": request.user.username,
        "action": "verdict_not_malware",
        "reason": request.POST.get("reason"),
        "created_at": str(now),
    }

    request.session.flash(
        f"Project {project.name} marked as not malware.\n"
        "Please update related Help Scout conversations.",
        queue="success",
    )
    return HTTPSeeOther(request.route_path("admin.malware_reports.list"))


@view_config(
    route_name="admin.malware_reports.detail.verdict_quarantine",
    permission=Permissions.AdminObservationsWrite,
    uses_session=True,
    require_csrf=True,
    require_methods=["POST"],
)
def verdict_quarantine_project(request):
    """
    Quarantine a Project. Reversible action.
    """
    observation_id = request.matchdict.get("observation_id")
    observation = request.db.get(Observation, observation_id)
    project = observation.related

    quarantine_project(project, request)
    return HTTPSeeOther(request.route_path("admin.malware_reports.list"))


@view_config(
    route_name="admin.malware_reports.detail.verdict_remove_malware",
    permission=Permissions.AdminProjectsDelete,
    uses_session=True,
    require_csrf=True,
    require_methods=["POST"],
)
def remove_malware_for_project(request):
    """
    Remove a Project as malware,
    freeze the Owner's account,
    add action to all Observations.
    """
    observation_id = request.matchdict.get("observation_id")
    observation = request.db.get(Observation, observation_id)
    project = observation.related

    confirm_project(project, request, fail_route="admin.malware_reports.detail")

    # freeze associated user accounts
    for user in project.users:
        user.is_frozen = True
        user.record_observation(
            request=request,
            kind=ObservationKind.AccountAbuse,
            actor=request.user,
            summary="Account frozen due to malware report",
            payload={"project_name": project.name},
        )

    now = datetime.now(tz=timezone.utc)

    # Obtain all related Malware Observations to update.
    project_malware_observations = {
        obs for obs in project.observations if obs.kind == "is_malware"
    }

    helpdesk_service = request.find_service(IHelpDeskService)
    # Each Action is a dictionary entry keyed by timestamp
    for observation in project_malware_observations:
        observation.actions[int(now.timestamp())] = {
            "actor": request.user.username,
            "action": "remove_malware",
            "created_at": str(now),
        }

        helpdesk_service.add_tag(
            conversation_url=observation.additional["helpscout_conversation_url"],
            tag="removed_as_malware_via_admin",
        )

    prohibit_and_remove_project(
        project, request, observation_kind=ObservationKind.IsMalware.value[0]
    )

    request.session.flash(
        f"Malware Project {project.name} removed and report updated",
        queue="success",
    )
    return HTTPSeeOther(request.route_path("admin.malware_reports.list"))
